<template>
  <div class="nodeManagement-container">
    <MCard class="header">
      <template #header>
        <span>查询节点信息</span>
      </template>
        <div class="header-left">
           <el-form 
           :inline="true" 
           :model="searchNodeForm" 
           :items="searchNodeFormItems"
           :rules="searchNodeFormRules"
           >
           <el-form-item prop="nodeId">
            <el-input v-model="searchNodeForm.nodeId" placeholder="输入节点ID"  clearable  />
           </el-form-item>
           <el-form-item prop="ipAddress">
            <el-input v-model="searchNodeForm.ipAddress" placeholder="输入IP地址" clearable />
           </el-form-item>
           <el-form-item prop="type">
            <MSelect v-bind="nodeTypeOptions" v-model="searchNodeForm.type" />
           </el-form-item>
           <el-form-item prop="connectionStatus">
            <MSelect v-bind="connectionStatusOptions" v-model="searchNodeForm.connectionStatus" />
           </el-form-item>
           <el-form-item>
            <div class="global_btn" style="width: 100px;"  v-globalBtnLoading="{handler:handleSearchNode,text:'查询中...'}">查询</div>
           </el-form-item>
           </el-form>
      </div>
    </MCard>
    <MCard >
      <template #header>
        <div class="flex gap-20px items-center">
          <span>节点列表</span>
        <el-radio-group v-model="nodeChooseConfig.nodeTypeRadio" @change="handleTypeChange">
            <el-radio value="3" size="large">全部节点</el-radio>
          <el-radio value="1" size="large">真实节点</el-radio>
          <el-radio value="0" size="large">虚拟节点</el-radio>
          </el-radio-group>
        </div>    
      </template> 
      <div class="flex gap-30px items-center">
         <div class="global_btn" v-globalBtnLoading="{ handler: handleDeletedNode, text: '删除中...' }">一键删除</div>
         <div class="tech-checkbox" @click="toggleSelectAll">
           <div class="checkbox-inner" :class="{'is-checked': nodeChooseConfig.isSelectAll}">
             <div class="check-mark" v-if="nodeChooseConfig.isSelectAll"></div>
           </div>
           <span class="checkbox-label">全选</span>
         </div>
      </div>
      <div class="grid grid-cols-4 gap-10px m-t-20px">
        <div class="deployedNodeItem" v-for="(item,index) in nodeList" :key="index">
            <div class="node-header">
              <div class="tech-checkbox" @click="toggleItemSelect(item)">
                <div class="checkbox-inner" :class="{'is-checked': item.isSelect}">
                  <div class="check-mark" v-if="item.isSelect"></div>
                </div>
              </div>
              <span class="node-id">节点ID: {{ item.nodeId }}</span>
              <div class="node-cpu-mem flex gap-10px" style="margin-left: auto;" v-if="item.type===1||item.type===2">
                <div>
                  <span class="info-label">cpu占用: </span>
                  <span :class="{'high-value': item.cpu >= 80, 'normal-value': item.cpu < 80}"> {{ item.cpu }}%</span>
                </div>
                <div>
                  <span class="info-label">磁盘占用: </span>
                  <span :class="{'high-value': item.meu >= 80, 'normal-value': item.meu < 80}"> {{ item.meu }}%</span>
                </div>
              </div>
            </div>
            <div class="node-divider"></div>
            <div class="node-content">
              <div class="node-info">
                <div class="info-grid">
                  <div class="info-item">
                    <span class="info-label">节点类型:</span>
                    <span class="info-value" :class="item.type===1||item.type===2 ? 'real-node' : 'virtual-node'">{{ item.type===1 ? '真实节点' : item.type===0 ? '虚拟节点' : '宿主机' }}</span>
                  </div>
                  <div class="info-item" v-for="(_,key) in nodeListItems" :key="key">
                    <span class="info-label">{{ nodeListItems[key] }}:</span>
                    <span class="info-value">{{ item[key] }}</span>
                  </div>
                </div>
                <div class="status-indicator" :class="item.connectionStatus === 1 ? 'connected' : 'disconnected'">
                  <img :src="item.imgUrl" alt="" class="status-icon">
                  <div class="status-pulse"></div>
                  <div class="status-text">{{ item.connectionStatus === 1 ? '已连接' : '未连接' }}</div>
                </div>
              </div>
              <div class="node-actions">
                <div class="global_btn" style="height: 25px;"  @click="handleNodeDetail(item)">详情</div>
                <div class="global_btn" v-show="item.type==0" style="height: 25px;" v-globalBtnLoading="{ handler: handleDeletedNode.bind(null,item), text: '删除中...' }">删除</div>
              </div>
            </div>
        </div>
      </div>
      <div class="flex justify-center m-t-20px">
        <el-pagination
            v-model:currentPage="nodePageData.currentPage"
            v-model:page-size="nodePageData.pageSize"
            :page-sizes="[3,5,7,10]"
            layout="total, prev, pager, next, jumper"
            :total="nodePageData.total"
            @size-change="handleGetNodePageList"
            @current-change="handleGetNodePageList"
            size="small"
           />
      </div>
    </MCard>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "NodeManagement",
});
</script>

<script setup lang="ts">
import MSelect from "@/components/MSelect.vue";
import { nodeManagementApi } from "@/api/nodeManagement";
import connectedImg from "@/assets/imgs/connected.png"
import disconnectedImg from "@/assets/imgs/disconnected.png"
import { useRoute,useRouter } from "vue-router"
import { ElNotificationFn } from "@/utils/computedUtils"
import dayjs from "dayjs"
import {getUserInfo} from "@/utils/token-utils"
import {getWebSocketInstance} from "@/utils/websocketExample"
const userId = getUserInfo().userid
const router = useRouter()
const wsClient = getWebSocketInstance()
const searchNodeForm = ref<any>({
  nodeId: "",
  ipAddress: "",
  type: "",
  connectionStatus: "",
  startTime: "",
  endTime: "",
});
const searchNodeFormRules = ref<any>({
  nodeId: [{ required: true, message: "输入节点ID", trigger: "blur" }],
  ipAddress: [{ required: true, message: "输入IP地址", trigger: "blur" }],
  type: [{ required: true, message: "选择节点类型", trigger: "blur" }],
  connectionStatus: [{ required: true, message: "选择连接状态", trigger: "blur" }],
});
const nodeTypeOptions = ref<any>({
  options: [
    {
      label: "虚拟节点",
      value: 0,
    },
    {
      label: "真实节点",
      value: 1,
    },
    {
      label: "宿主机节点",
      value: 2,
    },
    {
      label: "全部节点",
      value: "",
    },
  ],
  placeholder: "选择节点类型",
  width: "160px"
});
const connectionStatusOptions = ref<any>({
  options: [
    {
      label: "未连接",
      value: 0,
    },  
    {
      label: "已连接",
      value: 1,
    },
    {
      label: "全部",
      value: "",
    },
  ],
  placeholder: "选择连接状态",
  width: "160px"
});
const nodePageData = ref<any>({
  pageSize: 8,
  currentPage: 1,
  total:0
});
async function handleSearchNode(){
  await handleGetNodePageList()
}
const nodeList = ref<any[]>([])
const initialNodeList = ref<any[]>([])
const realNodeAndHostNodeList = ref<any[]>([])
async function handleGetNodePageList(){
  const {data} = await nodeManagementApi.getNodeListExcludeHost({
    ...nodePageData.value,
    ...searchNodeForm.value
  })
  realNodeAndHostNodeList.value = data.records.filter((item:any)=>item.type===1)
  initialNodeList.value = data.records.map((item:any)=>{
    return {
      ...item,
      isSelect:false,
      imgUrl:item.connectionStatus=== 1 ? connectedImg : disconnectedImg,
      updateTime:dayjs(item.updateTime).format("YYYY-MM-DD HH:mm:ss"),
    }
  })
  nodeList.value = initialNodeList.value
  nodePageData.value.total = data.total
  await getNodeCpuAndMem()
}
async function getNodeCpuAndMem(){
  const ips = realNodeAndHostNodeList.value.map((item:any)=>item.ipAddress)
  const {data} = await nodeManagementApi.getNodeCpuAndMem({
    ips,
    userId
  })
}
const nodeChooseConfig = ref<any>({
  nodeTypeRadio:"3",
  isSelectAll:false
})
const nodeListItems = ref<any>({
  ipAddress:"IP地址",
  deploymentFileNumber:"部署文件",
  updateTime:"更新时间",
})
function handleTypeChange(val:string){ 
  if(val === "3"){
    nodeList.value = initialNodeList.value
    return
  }
  nodeList.value = initialNodeList.value.filter((item:any)=>{
    return item.type == val
  })
}
function toggleItemSelect(item:any){
  item.isSelect = !item.isSelect
  nodeChooseConfig.value.isSelectAll = nodeList.value.every((node:any)=>node.isSelect)
}
function toggleSelectAll(){
  nodeList.value.forEach((item:any)=>{
    item.isSelect = !nodeChooseConfig.value.isSelectAll
  })
  nodeChooseConfig.value.isSelectAll = !nodeChooseConfig.value.isSelectAll
}
async function handleDeletedNode(item?:any){
  const nodeIds = item?.nodeId?[item.nodeId]:nodeList.value.filter((item:any)=>item.isSelect).map((item:any)=>item.nodeId)
  await nodeManagementApi.postBatchDeleteNode(nodeIds)
  ElNotificationFn("success","删除成功")
  await handleGetNodePageList()
}
function handleNodeDetail(item:any){
  router.push({
    path:"/nodeManagement/nodeDetail",
    query:{
      nodeId:item.nodeId
    }
  })
}
function handleGetCpu(data:any){
  const item = initialNodeList.value.find((item:any)=>item.ipAddress===data.ipAddress)
  if(item){
    item.cpu = parseFloat(Number(data.cpu).toFixed(2))
    item.meu = parseFloat(Number(data.meu).toFixed(2))
  }
}
const wsResponseHandler:any = {
  "JRLC.get.cpu":handleGetCpu,
}
wsClient.on("message",(responseData:any)=>{
  const {data,cmd} = responseData
  if(!Object.keys(wsResponseHandler).includes(cmd)) return
  wsResponseHandler[cmd](data)
})
onMounted(()=>{
  handleGetNodePageList()
})
</script>

<style lang="scss" scoped>
@import "@/styles/input.scss";
@import "@/styles/card.scss";
@import "@/styles/select.scss";
@import "@/styles/pagination.scss";
.nodeManagement-container {
  width: 100%;
  height: 100%;
  .header { 
    .header-left {
      .search-form {
        :deep(.el-form-item) {
          margin-bottom: 10px;
          margin-right: 15px;   
          .el-input__wrapper {
            height: 36px;
          }
        }
      }
      .search-btn {
        margin-right: 10px;
      }
    }
  }
  .deployedNodeItem {
  background: linear-gradient(
    180deg, 
    rgba(22, 30, 45, 0.8) 0%, 
    rgba(18, 28, 40, 0.85) 50%, 
    rgba(15, 25, 38, 0.8) 100%
  );
  border: 1px solid rgba(35, 85, 140, 0.5);
  border-radius: 4px;
  box-shadow: 
    inset 0 0 15px rgba(0, 140, 255, 0.08),
    0 0 10px rgba(0, 0, 0, 0.4),
    0 0 8px rgba(0, 150, 255, 0.1);
  overflow: hidden;
  position: relative;
  transition: all 0.3s ease;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg, 
      transparent, 
      rgba(0, 170, 255, 0.5), 
      transparent
    );
    opacity: 0.7;
    z-index: 2;
  }
  
  &::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-image: 
      linear-gradient(rgba(40, 100, 180, 0.02) 1px, transparent 1px),
      linear-gradient(90deg, rgba(40, 100, 180, 0.02) 1px, transparent 1px);
    background-size: 20px 20px;
    pointer-events: none;
    opacity: 0.5;
    z-index: 1;
  }
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 
      inset 0 0 20px rgba(0, 140, 255, 0.12),
      0 5px 15px rgba(0, 0, 0, 0.4),
      0 0 15px rgba(0, 150, 255, 0.15);
    
    .node-id {
      color: #fff;
      text-shadow: 0 0 8px rgba(0, 180, 255, 0.6);
    }
    
    .node-divider {
      background: linear-gradient(90deg, 
        transparent, 
        rgba(0, 180, 255, 0.6), 
        transparent
      );
      box-shadow: 0 0 8px rgba(0, 180, 255, 0.4);
    }
  }
  
  .node-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px;
    position: relative;
    z-index: 2;
  }
  
  .node-id {
    color: rgba(255, 255, 255, 0.9);
    font-family: 'Consolas', monospace;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0.5px;
    text-shadow: 0 0 5px rgba(0, 140, 255, 0.3);
    transition: all 0.3s;
  }
  
  .node-divider {
    height: 1px;
    width: 90%;
    margin: 0 auto;
    background: linear-gradient(90deg, 
      transparent, 
      rgba(0, 150, 255, 0.4), 
      transparent
    );
    transition: all 0.3s;
  }
  
  .node-content {
    padding: 12px;
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 15px;
  }
  
  .node-info {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  
  .info-grid {
    display: grid;
    gap: 8px;
  }
  
  .info-item {
    display: flex;
    align-items: center;
    gap: 8px; 
    .real-node {
      color: rgba(0, 255, 80, 0.9);
      text-shadow: 
        0 0 5px rgba(0, 255, 80, 0.5),
        0 0 10px rgba(0, 255, 80, 0.3);
      font-weight: 500;
      letter-spacing: 0.5px;
      position: relative;
      padding-left: 15px;
    } 
    .virtual-node {
      color: rgba(255, 80, 80, 0.9);
      text-shadow: 
        0 0 5px rgba(255, 80, 80, 0.5),
        0 0 10px rgba(255, 80, 80, 0.3);
      font-weight: 500;
      letter-spacing: 0.5px;
      position: relative;
      padding-left: 15px;
    }
  }
  
  .info-label {
    color: rgba(100, 180, 255, 0.9);
    font-size: 13px;
    font-weight: 500;
  }
  
  .info-value {
    color: rgba(255, 255, 255, 0.85);
    font-size: 13px;
    font-family: 'Consolas', monospace;
  }
  .info-value {
  color: #ffffff;
  font-size: 15px;
  font-family: 'Consolas', monospace;
  letter-spacing: 0.5px;
  padding-left: 14px;
  position: relative;
  text-shadow: 0 0 5px rgba(0, 180, 255, 0.3);
  transition: all 0.3s;
  
  &::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 8px;
    height: 1px;
    background: rgba(0, 180, 255, 0.5);
  }
}
  
  .status-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 50px;
    height: 50px;
    
    &.connected {
      .status-icon {
        filter: drop-shadow(0 0 3px #00ff4c);
      }    
      /* .status-pulse {
        border: 2px solid rgba(0, 255, 76, 0.7);
        animation: pulse-green 2s infinite;
      }  */
      .status-text {
        color: #01AA78; 
      }
    }   
    &.disconnected {
      .status-icon {
        filter: drop-shadow(0 0 3px #ff4545);
      }      
    /*   .status-pulse {
        border: 2px solid rgba(255, 69, 69, 0.7);
        animation: pulse-red 2s infinite;
      }      */
      .status-text {
        color: #DD675E;
      }
    }
  }
  
  .status-icon {
    width: 24px;
    height: 24px;
    position: relative;
    z-index: 2;
    margin-bottom: 5px;
  }
  
  .status-text {
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.5px;
    font-family: 'Consolas', monospace;
    position: relative;
    z-index: 2;
    white-space: nowrap;
  }
  
  .status-pulse {
    position: absolute;
    top: 12px; /* 调整位置以匹配图标 */
    left: 50%;
    transform: translate(-50%, -50%);
    width: 30px;
    height: 30px;
    border-radius: 50%;
    opacity: 0;
  }
  
  .node-actions {
    display: flex;
    gap: 10px;
    margin-top: 5px;
  }
  }
  .node-cpu-mem {
    display: flex;
    gap: 15px;
    align-items: center;
    
    .high-value {
      color: #ff4545;
      text-shadow: 
        0 0 5px rgba(255, 69, 69, 0.7),
        0 0 10px rgba(255, 69, 69, 0.5);
      font-weight: 500;
      transition: all 0.3s ease;
      padding: 3px 8px;
      border-radius: 4px;
      background: rgba(255, 69, 69, 0.1);
      box-shadow: 0 0 8px rgba(255, 69, 69, 0.2);
      
      b {
        font-size: 15px;
      }
    }
    
    .normal-value {
      color: #00ff4c;
      text-shadow: 
        0 0 5px rgba(0, 255, 76, 0.7),
        0 0 10px rgba(0, 255, 76, 0.5);
      font-weight: 500;
      transition: all 0.3s ease;
      padding: 3px 8px;
      border-radius: 4px;
      background: rgba(0, 255, 76, 0.1);
      box-shadow: 0 0 8px rgba(0, 255, 76, 0.2);
      
      b {
        font-size: 15px;
      }
    }
  }
}
</style>
